﻿namespace Centido.Core.Test
{
    using System;
    using System.Data.Common;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Testing domain
    /// </summary>
    [TestClass]
    public class DomainTest
    {
        /// <summary>
        ///   Domain name
        /// </summary>
        public const string DomainName = "FullAdminRights";

        /// <summary>
        ///   Domain
        /// </summary>
        private Domain _domain;

        /// <summary>
        /// Test initialiaze
        /// </summary>
        [TestInitialize]
        public void Initialize()
        {
            _domain = Domain.Build(DomainConfiguration.Load(DomainName));

            _domain.DeleteAllTables();
        }

        /// <summary>
        /// Test clean up
        /// </summary>
        [TestCleanup]
        public void CleanUp()
        {
            _domain.DeleteAllTables();
        }

        /// <summary>
        /// Opening domain from the config file
        /// </summary>
        [TestMethod]
        public void OpeningFromConfigFile()
        {
            Assert.IsNotNull(_domain);
            Assert.IsNotNull(_domain.Name);
        }

        /// <summary>
        /// Passing null domain configuration
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void PassingEmptyDomainConfiguration()
        {
            Domain.Build(null);
        }

        /// <summary>
        /// Reading database name
        /// </summary>
        [TestMethod]
        public void ReadingDbName()
        {
            Assert.IsNotNull(_domain.DbName);
        }

        /// <summary>
        /// Reading dbms name
        /// </summary>
        [TestMethod]
        public void ReadingDbmsName()
        {
            Assert.IsNotNull(_domain.Dbms.ProductName);
        }

        /// <summary>
        /// Reading dbms version
        /// </summary>
        [TestMethod]
        public void ReadingDbmsVersion()
        {
            Assert.IsNotNull(_domain.Dbms.ProductVersion);
        }

        /// <summary>
        /// Reading dbms features
        /// </summary>
        [TestMethod]
        public void ReadingDbmsFeatures()
        {
            // ReSharper disable NotAccessedVariable
            var isEnabled = _domain.Dbms.HasColumnRenaming;
            // ReSharper restore NotAccessedVariable
        }

        /// <summary>
        /// Reading database provider name
        /// </summary>
        [TestMethod]
        public void ReadingDbProviderName()
        {
            Assert.IsNotNull(_domain.DbProviderName);

            var config = DomainConfiguration.Load(DomainName);

            Assert.AreEqual(_domain.DbProviderName, config.DbProviderName);
        }


        /// <summary>
        /// Reading database schema name
        /// </summary>
        [TestMethod]
        public void ReadingDbSchemaName()
        {
            Assert.IsNotNull(_domain.DbSchemaName);

            var config = DomainConfiguration.Load(DomainName);

            Assert.AreEqual(_domain.DbSchemaName, config.DbSchemaName);
        }

        /// <summary>
        /// Adding new table by name
        /// </summary>
        [TestMethod]
        public void AddingTableByName()
        {
            //New table name
            var tableName = StringProperty.RandomIdentifier();

            //Checking that there is no table with that name
            var tableWithThatName = _domain.GetTable(tableName);

            Assert.IsNull(tableWithThatName);

            //Creating new table
            var table = _domain.AddTable(tableName);

            Assert.IsNotNull(table);
            Assert.AreEqual(table.Name, tableName.ToLower());

            //Searching table by name
            var tableFoundByName = _domain.GetTable(tableName);

            Assert.IsNotNull(tableFoundByName);
            Assert.AreEqual(tableFoundByName.Id, table.Id);
            Assert.AreEqual(tableFoundByName.Name, tableName.ToLower());

            //Searching table by id
            var tableFoundById = _domain.GetTable(table.Id);

            Assert.IsNotNull(tableFoundById);
            Assert.AreEqual(tableFoundById.Id, table.Id);
            Assert.AreEqual(tableFoundById.Name, tableName.ToLower());

            //Checking table count
            var currentTableCount = _domain.Tables.Count();

            Assert.AreEqual(currentTableCount, 1);

            //Checking table existence in a table list
            var tableExistsInTableList = _domain.Tables.Any(t => t.Name == tableName.ToLower());

            Assert.IsTrue(tableExistsInTableList);
            Assert.IsTrue(table.Exists());
        }

        /// <summary>
        /// Adding new table by id and name
        /// </summary>
        [TestMethod]
        public void AddingTableByIdAndName()
        {
            //Table id
            var tableId = Guid.NewGuid();

            //Table name
            var tableName = StringProperty.RandomIdentifier();

            var table = _domain.AddTable(tableId, tableName);

            Assert.IsNotNull(table);
            Assert.AreEqual(table.Id, tableId);
            Assert.AreEqual(table.Name, tableName.ToLower());
            Assert.IsTrue(table.Exists());
        }

        /// <summary>
        /// Adding table with parameters
        /// </summary>
        [TestMethod]
        public void AddingTableWithParameters()
        {
            var tableParams = new TableParameters();

            var table = _domain.AddTable(StringProperty.RandomIdentifier(), tableParams);

            Assert.IsNotNull(table);
            Assert.IsTrue(table.Exists());

            Assert.AreEqual(table.SystemColumns.Id.Id, tableParams.IdColumnId);
            Assert.AreEqual(table.SystemColumns.CreatedBy.Id, tableParams.CreatedByColumnId);
            Assert.AreEqual(table.SystemColumns.CreatedOn.Id, tableParams.CreatedOnColumnId);
            Assert.AreEqual(table.SystemColumns.UpdatedBy.Id, tableParams.UpdatedByColumnId);
            Assert.AreEqual(table.SystemColumns.UpdatedOn.Id, tableParams.UpdatedOnColumnId);
            Assert.AreEqual(table.SystemColumns.DeletedBy.Id, tableParams.DeletedByColumnId);
            Assert.AreEqual(table.SystemColumns.DeletedOn.Id, tableParams.DeletedOnColumnId);
            Assert.AreEqual(table.SystemColumns.Owner.Id, tableParams.OwnerColumnId);
            Assert.AreEqual(table.SystemColumns.RowVersion.Id, tableParams.RowVersionColumnId);
            Assert.AreEqual(table.SystemColumns.IsActive.Id, tableParams.IsActiveColumnId);
        }

        /// <summary>
        /// Adding table without default indexes
        /// </summary>
        [TestMethod]
        public void AddingTableWithoutDefaultIndexes()
        {
            var tableParams = new TableParameters {DefaultIndexes = false};


            var table = _domain.AddTable(Guid.NewGuid(), StringProperty.RandomIdentifier(), tableParams);

            Assert.IsNotNull(table);
            Assert.AreEqual(table.Indexes.Count(), 0);
        }

        /// <summary>
        /// Adding table with default indexes
        /// </summary>
        [TestMethod]
        public void AddingTableWithDefaultIndexes()
        {
            var tableParams = new TableParameters();

            var table = _domain.AddTable(Guid.NewGuid(), StringProperty.RandomIdentifier(), tableParams);

            Assert.IsNotNull(table);
            Assert.AreEqual(table.Indexes.Count(), 2);

            Assert.AreEqual(table.Indexes.Count(i => i.Columns.Count() == 1 
                && i.Columns.Count(c => c.Id == table.SystemColumns.Id.Id) == 1)
                , 1);
            Assert.AreEqual(table.Indexes.Count(i => i.Columns.Count() == 1
                && i.Columns.Count(c => c.Id == table.SystemColumns.IsActive.Id) == 1)
                , 1);
        }

        /// <summary>
        /// Adding two tables
        /// </summary>
        [TestMethod]
        public void AddingTwoTables()
        {
            //Creating two tables
            var firstTable = _domain.AddTable("first_table");
            var secondTable = _domain.AddTable("second_table");

            Assert.IsNotNull(firstTable);
            Assert.IsNotNull(secondTable);

            Assert.AreEqual(firstTable.Name, "first_table");
            Assert.AreEqual(secondTable.Name, "second_table");

            //Checking table count
            Assert.AreEqual(_domain.Tables.Count(), 2);

            //Removing second table
            secondTable.Delete();

            //Checking table colunt
            Assert.AreEqual(_domain.Tables.Count(), 1);

            //Writing some property in the first table
            firstTable.Description = "description";

            Assert.AreEqual(firstTable.Description, "description");
        }

        /// <summary>
        /// Adding table with too long name
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException), AllowDerivedTypes = true)]
        public void AddingTableWithTooLongName()
        {
            _domain.AddTable(StringProperty.TooLongIdentifier('a'));
        }

        /// <summary>
        /// Passing null when adding table
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void PassingNullWhenAddingTable()
        {
            _domain.AddTable(null);
        }

        /// <summary>
        /// Passing a keyword when adding a table
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void PassingKeywordWhenAddingTable()
        {
            _domain.AddTable("SELECT");
        }

        /// <summary>
        /// Passing empty string when adding table
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void PassingEmptyStringWhenAddingTable()
        {
            _domain.AddTable(" \t");
        }

        /// <summary>
        /// Passing forbidden symbol when adding a table
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void PassingForbiddenSymbolWhenAddingTable()
        {
            _domain.AddTable("a a");
        }

        /// <summary>
        /// Passing wrong start symbol when adding a table
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void PassingWrongStartSymbolWhenAddingTable()
        {
            _domain.AddTable("_a");
        }

        /// <summary>
        ///   Passing non-english letters when creating a table.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void PassingNonEnglishLettersWhenAddingTable()
        {
            _domain.AddTable("mя");
        }


        /// <summary>
        /// Checking table name trimming and lowering when adding new table
        /// </summary>
        [TestMethod]
        public void CheckingTableNameTrimmingAndLoweringWhenAddingTable()
        {
            //Table name
            const string tableName = " \tA\t";

            //Trimmed and lowered table name
            const string trimmedAndLoweredTableName = "a";

            _domain.AddTable(tableName);

            //Searching for table with initial name
            var foundTable = _domain.GetTable(tableName);

            Assert.IsNotNull(foundTable);
            Assert.AreNotEqual(foundTable.Name, tableName);
            Assert.AreEqual(foundTable.Name, trimmedAndLoweredTableName);
        }

        /// <summary>
        /// Adding table with duplicate id
        /// </summary>
        [TestMethod]
        public void AddingTableWithDuplicateId()
        {
            var table = _domain.AddTable(StringProperty.RandomIdentifier());

            try
            {
                _domain.AddTable(table.Id, StringProperty.RandomIdentifier());
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.AddingTableWithDuplicateId);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Adding table with duplicate name
        /// </summary>
        [TestMethod]
        public void AddingTableWithDuplicateName()
        {
            const string tableName = "my_table";

            //Adding first table
            _domain.AddTable(tableName);

            try
            {
                //Inserting duplicate table
                _domain.AddTable(tableName);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.AddingTableWithDuplicateName);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Inserting new value into new table
        /// </summary>
        [TestMethod]
        public void InsertingValueIntoNewTable()
        {
            //New table name
            const string tableName = "new_table";

            var table = _domain.AddTable(tableName);

            table.Grant(DataAction.Insert);
            table.Grant(DataAction.Select);

            //Inserting new value specifying id
            var id = Guid.NewGuid();
            table.InsertRow(id);

            Assert.IsTrue(table.ContainsRow(id));
        }

        /// <summary>
        /// Searching table by id
        /// </summary>
        [TestMethod]
        public void SearchingTableById()
        {
            //Adding a table
            var table = _domain.AddTable("my_table");

            //Adding another table
            var anotherTableId = Guid.NewGuid();

            _domain.AddTable(anotherTableId, "another_table");

            //Searching tables by id
            Assert.IsNotNull(_domain.GetTable(table.Id));
            Assert.AreEqual(_domain.GetTable(table.Id).Id, table.Id);
            Assert.IsNotNull(_domain.GetTable(anotherTableId));
            Assert.AreEqual(_domain.GetTable(anotherTableId).Id, anotherTableId);

            //Searching non-existent table id
            Assert.IsNull(_domain.GetTable(Guid.NewGuid()));
        }

        /// <summary>
        /// Searching table by name
        /// </summary>
        [TestMethod]
        public void SearchingTableByName()
        {
            //Adding a table
            _domain.AddTable("my_table");

            //Adding another table
            _domain.AddTable("another_table");

            //Searching tables by name
            Assert.IsNotNull(_domain.GetTable("my_table"));
            Assert.AreEqual(_domain.GetTable("my_table").Name, "my_table");
            Assert.IsNotNull(_domain.GetTable("another_table"));
            Assert.AreEqual(_domain.GetTable("another_table").Name, "another_table");

            //Searching non-existent table name
            Assert.IsNull(_domain.GetTable("my_table2"));
        }

        /// <summary>
        /// Removing table by name
        /// </summary>
        [TestMethod]
        public void RemovingTableByName()
        {
            //Creating new table
            var tableName = StringProperty.RandomIdentifier();

            var table = _domain.AddTable(tableName);

            //Removing table
            _domain.DeleteTable(tableName);

            //Checking table count
            var tableCount = _domain.Tables.Count();

            Assert.AreEqual(tableCount, 0);

            //Trying to find the table
            var foundTable = _domain.GetTable(tableName);

            Assert.IsNull(foundTable);

            foundTable = _domain.GetTable(table.Id);

            Assert.IsNull(foundTable);

            //Checking table existence in a table list
            var tableIsInTableList = _domain.Tables.Any(t => t.Name == tableName);

            Assert.IsFalse(tableIsInTableList);
            Assert.IsFalse(table.Exists());
        }

        /// <summary>
        /// Remove table by id
        /// </summary>
        [TestMethod]
        public void RemovingTableById()
        {
            //Creating new table
            var tableName = StringProperty.RandomIdentifier();

            var table = _domain.AddTable(tableName);

            //Removing table
            _domain.DeleteTable(table.Id);

            //Checking table count
            var tableCount = _domain.Tables.Count();

            Assert.AreEqual(tableCount, 0);

            //Trying to find the table
            var foundTable = _domain.GetTable(tableName);

            Assert.IsNull(foundTable);

            foundTable = _domain.GetTable(table.Id);

            Assert.IsNull(foundTable);

            //Checking table existence in a table list
            var tableIsInTableList = _domain.Tables.Any(t => t.Name == tableName);

            Assert.IsFalse(tableIsInTableList);
            Assert.IsFalse(table.Exists());
        }

        /// <summary>
        /// Removing table by its reference
        /// </summary>
        [TestMethod]
        public void RemovingTableByReference()
        {
            //Creating new table
            var tableName = StringProperty.RandomIdentifier();

            var table = _domain.AddTable(tableName);

            //Removing table
            _domain.DeleteTable(table);

            //Checking table count
            var tableCount = _domain.Tables.Count();

            Assert.AreEqual(tableCount, 0);

            //Trying to find the table
            var foundTable = _domain.GetTable(tableName);

            Assert.IsNull(foundTable);

            foundTable = _domain.GetTable(table.Id);

            Assert.IsNull(foundTable);

            //Checking table existence in a table list
            var tableIsInTableList = _domain.Tables.Any(t => t.Name == tableName);

            Assert.IsFalse(tableIsInTableList);
            Assert.IsFalse(table.Exists());
        }

        /// <summary>
        /// Removing table that doesn't exist
        /// </summary>
        [TestMethod]
        public void RemovingTableThatDoesntExist()
        {
            //Creating new table
            var table = _domain.AddTable("new_table");

            //Removing that table
            table.Delete();

            //Checking that table was removed
            Assert.AreEqual(_domain.Tables.Count(), 0);
            Assert.IsNull(_domain.GetTable("new_table"));
            Assert.IsFalse(_domain.Tables.Any(t => t.Name == "new_table"));

            try
            {
                //Removing that table once again
                table.Delete();
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.TableWasNotFound);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Passing null when removing table
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void PassingNullWhenRemovingTable()
        {
            _domain.DeleteTable(tableName: null);
        }

        /// <summary>
        /// Passing empty string when removing table
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void PassingEmptyStringWhenRemovingTable()
        {
            _domain.DeleteTable(" \t");
        }

        /// <summary>
        /// Checking trimming and lowering of table name when removing a table
        /// </summary>
        [TestMethod]
        public void CheckingTableNameTrimmingAndLoweringWhenRemovingTable()
        {
            const string tableName = " \tA \t";

            const string trimmedAndLoweredTableName = "a";

            //Creating table
            _domain.AddTable(tableName);

            var foundTable = _domain.GetTable(tableName);

            Assert.IsNotNull(foundTable);
            Assert.AreEqual(foundTable.Name, trimmedAndLoweredTableName);

            //Removing table
            _domain.DeleteTable(tableName);

            foundTable = _domain.GetTable(tableName);

            Assert.IsNull(foundTable);
        }
    }
}